#ifndef __SERVICE_HPP__
#define __SERVICE_HPP__

/*主要实现注册服务端请求函数*/

#include "config.hpp"
#include "cpp-httplib/httplib.h"
#include "data.hpp"

namespace cloud
{
    class Service
    {
    private:
        int _server_port;
        std::string _server_ip;
        std::string _url_prefix;
        httplib::Server _server;

    public:
        static void Upload(const httplib::Request &req, httplib::Response &rsp)
        {
            // post /upload  文件数据在正文中（正文并不全是文件数据）
            std::string tmp_path = Config::GetInstance()->GetTmpPath();
            std::string realpath;
            BackupInfo info;

            if (req.has_file("file") == true) // 表单上传
            {
                const auto &file = req.get_file_value("file");
                // file.filename//文件名称    file.content//文件数据
                realpath = tmp_path + FileUtil(file.filename).FileName();
                FileUtil fu(realpath);
                fu.SetContent(file.content); // 将数据写入文件中；
            }
            else if (req.headers.count("filename") != 0) // 自定义报头上传
            {
                realpath = tmp_path + FileUtil(req.get_header_value("filename")).FileName();
                FileUtil fu(realpath);
                fu.SetContent(req.body); // 将数据写入文件中；
            }
            else
            {
                Log::getLogger("globalLogger")->warn("%s Upload失败!!!", req.remote_addr.c_str());
                rsp.status = 400;
                return;
            }

            rsp.status = 200;
            info.initByRealPath(realpath);            // 组织备份的文件信息
            DataManager::GetInstance()->Insert(info); // 向数据管理模块添加备份的文件信息
            Log::getLogger("globalLogger")->_info("%s Upload: %s成功!!!", req.remote_addr.c_str(), FileUtil(realpath).FileName().c_str());
        }

        static std::string TimetoStr(time_t t)
        {
            std::string tmp = std::ctime(&t);
            tmp.pop_back(); // 删除尾部的换行符
            return tmp;
        }

        static void ClientListShow(const httplib::Request &req, httplib::Response &rsp) // 客户端
        {
            // 获取所有的文件备份信息
            std::vector<BackupInfo> arry;
            DataManager::GetInstance()->GetAll(&arry);

            // 组织json
            Json::Value root;
            for (auto &file : arry)
            {
                Json::Value item;
                item["filename"] = file._filename;
                item["time"] = file._time;
                item["size"] = std::to_string(file._fsize);
                root.append(item);
            }
            JsonUtil::Serialize(root, &rsp.body);
            Log::getLogger("globalLogger")->_info("%s 请求展示文件成功!!!", req.remote_addr.c_str());
            rsp.status = 200;
        }

        static void ListShow(const httplib::Request &req, httplib::Response &rsp) // 浏览器
        {
            if (req.method == "GET")
            {
                // 1. 获取所有的文件备份信息
                std::vector<BackupInfo> arry;
                DataManager::GetInstance()->GetAll(&arry);

                // 2. 读取HTML模板文件内容
                std::ifstream file("listShow.html");
                std::stringstream buffer;
                buffer << file.rdbuf();
                std::string html_template = buffer.str();

                // 3. 生成文件信息HTML片段
                std::stringstream ss;
                for (auto &file : arry)
                {
                    ss << "<tr>";
                    std::string filename = file._filename;
                    ss << "<td><a href='" << file._url << "'>" << filename << "</a></td>";
                    ss << "<td align='right'>" << file._time << "</td>";
                    ss << "<td align='right'>" << file._fsize / 1024 << "k</td>";
                    ss << "</tr>";
                }
                std::string file_info_html = ss.str();

                // 4. 替换模板中的占位符
                size_t pos = html_template.find("<!-- DATA_PLACEHOLDER -->");
                if (pos != std::string::npos)
                {
                    html_template.replace(pos, strlen("<!-- DATA_PLACEHOLDER -->"), file_info_html);
                }

                // 5. 设置响应
                rsp.body = html_template;
                rsp.set_header("Content-Type", "text/html");
                rsp.status = 200;
                Log::getLogger("globalLogger")->_info("%s 请求展示文件成功!!!", req.remote_addr.c_str());
            }
            else
            {
                rsp.status = 405;
                rsp.set_content("Method Not Allowed", "text/plain");
                Log::getLogger("globalLogger")->warn("%s 请求方法不允许!!!", req.remote_addr.c_str());
            }
        }

        static void CheckFile(const httplib::Request &req, httplib::Response &rsp)
        {
            if (req.method == "POST")
            {
                Json::Value root;
                // 解析JSON
                JsonUtil::UnSerialize(root, &req.body);
                std::string md5 = root["md5"].asCString();
                std::string filename = root["fileName"].asCString();
                size_t size = root["fileSize"].asUInt();

                // 询问数据库是否存在该文件
                bool exists = SqlManager::GetInstance()->hasfile(md5);
                if (exists == true)
                {
                    SqlManager::GetInstance()->InsertFile(filename, md5, size);
                }
                Json::Value response;
                response["exists"] = exists;
                std::string rescontent;
                JsonUtil::Serialize(response, &rescontent);
                rsp.set_content(rescontent, "application/json");
                rsp.status = 200;
            }
            else
            {
                rsp.status = 405;
                rsp.set_content("Method Not Allowed", "text/plain");
            }
        }

        // 主要用来判断文件是否被修改,与断点续传有关
        static std::string GetETag(const BackupInfo &info)
        {
            // etg :  filename-fsize-mtime
            std::string etag = info._filename;
            etag += "-";
            etag += std::to_string(info._fsize);
            etag += "-";
            // etag += std::to_string(info._time);
            etag += info._time;
            return etag;
        }

        // 下载文件
        static void Download(const httplib::Request &req, httplib::Response &rsp)
        {
            BackupInfo Info;
            DataManager::GetInstance()->GetOneByURL(req.path, &Info);

            bool retrans = false;
            std::string old_etag;
            if (req.has_header("If-Range"))
            {
                old_etag = req.get_header_value("If-Range");
                // 有If-Range字段且，这个字段的值与请求文件的最新etag一致则符合断点续传
                if (old_etag == GetETag(Info))
                {
                    retrans = true;
                }
            }

            // 4. 读取文件数据，放入rsp.body中
            if (retrans == false)
            {
                // 先从redis中获取看看缓存中有没有
                std::string md5;
                std::string tmp; // 用于存放压缩后的数据,即数据库的存的数据
                if (RedisUtil::GetInstance()->Get(Info._filename, md5) == true)
                {
                    // 存在md5,此时
                    if (RedisUtil::GetInstance()->Get(md5, tmp) == false)
                    {
                        // 没有内容缓存再从mysql中取
                        SqlManager::GetInstance()->GetFileContentByMD5(md5, tmp);
                    }
                }
                else // 不存在md5直接从mysql中取
                {
                    SqlManager::GetInstance()->GetFileContentByFileName(Info._filename, tmp);
                }
                // 对数据进行解压
                BundleUtil::UnCompress(tmp, rsp.body);
                // 5. 设置响应头部字段： ETag， Accept-Ranges: bytes
                rsp.set_header("Accept-Ranges", "bytes");
                rsp.set_header("ETag", GetETag(Info));
                rsp.set_header("Content-Type", "application/octet-stream");
                rsp.status = 200;
                Log::getLogger("globalLogger")->_info("%s 下载文件%s 成功!!!", req.remote_addr.c_str(), Info._filename.c_str());
            }
            else
            {
                // httplib内部实现了对于区间请求也就是断点续传请求的处理
                // 只需要我们用户将文件所有数据读取到rsp.body中，它内部会自动根据请求
                // 区间，从body中取出指定区间数据进行响应
                //  std::string  range = req.get_header_val("Range"); bytes=start-end

                // 先从redis中获取看看缓存中有没有
                std::string md5;
                std::string tmp; // 用于存放压缩后的数据,即数据库的存的数据
                if (RedisUtil::GetInstance()->Get(Info._filename, md5) == true)
                {
                    // 存在md5,此时
                    if (RedisUtil::GetInstance()->Get(md5, tmp) == false)
                    {
                        // 没有内容缓存再从mysql中取
                        SqlManager::GetInstance()->GetFileContentByMD5(md5, tmp);
                    }
                }
                else // 不存在md5直接从mysql中取
                {
                    SqlManager::GetInstance()->GetFileContentByFileName(Info._filename, tmp);
                }
                // 对数据进行解压
                BundleUtil::UnCompress(tmp, rsp.body);
                rsp.set_header("Accept-Ranges", "bytes");
                rsp.set_header("ETag", GetETag(Info));
                rsp.set_header("Content-Type", "application/octet-stream");
                // rsp.set_header("Content-Range", "bytes start-end/fsize");
                rsp.status = 206; // 区间请求响应的是206*****
                Log::getLogger("globalLogger")->_info("%s 断点续传%s 成功!!!", req.remote_addr.c_str(), Info._filename.c_str());
            }
        }

        static void UploadPage(const httplib::Request &req, httplib::Response &rsp)
        {
            if (req.method == "GET")
            {
                std::ifstream file("upLoad.html");
                std::stringstream buffer;
                buffer << file.rdbuf();
                rsp.body = buffer.str();
                rsp.set_header("Content-Type", "text/html");
                rsp.status = 200;
            }
            else
            {
                rsp.status = 405;
                rsp.set_content("Method Not Allowed", "text/plain");
            }
        }

    public:
        Service()
        {
            Config *conf = Config::GetInstance();
            _server_ip = conf->GetServerIp();
            _server_port = conf->GetServerPort();
            _url_prefix = conf->GetDownloadPrefix();
        }
        bool RunModule()
        {
            // 注册请求函数
            _server.Post("/upload", Upload);
            _server.Get("/listshow", ListShow);
            _server.Get("/clientShow", ClientListShow);
            _server.Get("/upload_page", UploadPage);
            _server.Post("/check_file", CheckFile);
            _server.Get("/", ListShow);                      // 默认也是显示文件列表
            std::string download_url = _url_prefix + "(.*)"; //(.*)正则表达式,表示匹配任意字符
            _server.Get(download_url, Download);

            // 开始监听
            Log::getLogger("globalLogger")->_info("开始监听");

            _server.listen(_server_ip.c_str(), _server_port);
            return true;
        }
    };

} // namespace cloud

#endif //__SERVICE_HPP__